package com.example.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import org.bouncycastle.jcajce.io.CipherInputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;

/**
 * @ClassName SM4DecryptEncryptFile
 * @Description TODO
 * @Author kun
 * @Date 2024-10-24 15:23
 * @Version 1.0
 */
@Component
public class SM4DecryptEncryptFile {

    /**
     * 加密文件。
     * 使用提供的密钥对指定输入文件进行加密，并将加密后的数据写入到输出文件中。
     *
     * @param inputFilePath 输入文件的路径。
     * @param encPath       输出文件的路径，即加密后的文件将保存在这个路径下。
     */
    public boolean encryptFile(String inputFilePath, String encPath, String password) {
        // 加密文件
        try {
            byte[] keyDatatmp = ByteUtils.fromHexString(password);
            // 根据密钥和加密模式生成Cipher实例
            Cipher cipher = generateCipher(Cipher.ENCRYPT_MODE, keyDatatmp);
            // 创建CipherInputStream，用于从输入文件读取数据并加密
            CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(inputFilePath), cipher);
            File file = new File(inputFilePath);
            // 将加密后的数据写入到输出文件
            FileUtil.writeFromStream(cipherInputStream, encPath);
            // 关闭CipherInputStream
            IoUtil.close(cipherInputStream);
            return true;
        } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | NoSuchProviderException |
                 FileNotFoundException e) {
            e.printStackTrace();

        }
        return false;
    }


    public boolean decryptFile(String inputFilePath, String outputFilePath, String password) {
        /*
         * 存储加密/解密所使用的密钥数据。
         * 这里通过ByteUtils从十六进制字符串转换为字节数组，初始化密钥数据。
         * 请注意，该密钥数据的来源和安全性应在使用前得到确保。
         */
        byte[] keyDataTmp = ByteUtils.fromHexString(password);
        // 初始化输入输出流及相关加密对象
        FileInputStream in = null;
        ByteArrayInputStream byteArrayInputStream = null;
        OutputStream out = null;
        CipherOutputStream cipherOutputStream = null;
        try {
            // 创建文件输入流，用于读取待解密文件
            in = new FileInputStream(inputFilePath);
            // 读取文件内容到字节数组
            byte[] bytes = IoUtil.readBytes(in);
            // 将字节数组转换为字节输入流
            byteArrayInputStream = IoUtil.toStream(bytes);
            // 创建解密器，并初始化为解密模式
            Cipher cipher = generateCipher(Cipher.DECRYPT_MODE, keyDataTmp);
            // 创建文件输出流，用于写入解密后的数据
            out = Files.newOutputStream(Paths.get(outputFilePath));
            // 将解密器和文件输出流组合成加密输出流
            cipherOutputStream = new CipherOutputStream(out, cipher);
            // 复制字节输入流到加密输出流，实现解密过程
            IoUtil.copy(byteArrayInputStream, cipherOutputStream);
            return true;
        } catch (IOException e) {
            // 处理IO异常
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            // 处理加密/解密算法填充方式不存在的异常
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // 处理加密/解密算法不存在的异常
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            // 处理密钥无效的异常
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            // 处理加密/解密服务提供者不存在的异常
            e.printStackTrace();
        } finally {
            // 关闭资源，确保释放系统资源
            IoUtil.close(cipherOutputStream);
            IoUtil.close(out);
            IoUtil.close(byteArrayInputStream);
            IoUtil.close(in);
        }
        return false;
    }


    /**
     * 根据给定的模式和密钥数据生成Cipher实例。
     * 该方法用于初始化Cipher对象，以供加密或解密操作使用。
     *
     * @param mode    Cipher的操作模式，如ENCRYPT_MODE或DECRYPT_MODE。
     * @param keyData 用于生成密钥的字节数组。
     * @return 初始化后的Cipher对象。
     * @throws InvalidKeyException      如果密钥无效。
     * @throws NoSuchPaddingException   如果指定的填充方式不存在。
     * @throws NoSuchAlgorithmException 如果指定的算法不存在。
     * @throws NoSuchProviderException  如果指定的提供者不存在。
     */
    private static Cipher generateCipher(int mode, byte[] keyData) throws
            InvalidKeyException,
            NoSuchPaddingException,
            NoSuchAlgorithmException,
            NoSuchProviderException {
        // 实例化Cipher对象，使用SM4算法、ECB模式和PKCS5填充方式。
        Cipher cipher = Cipher.getInstance("SM4/ECB/PKCS5Padding", BouncyCastleProvider.PROVIDER_NAME);
        // 根据密钥数据创建SecretKeySpec对象，用于生成SM4密钥。
        Key sm4Key = new SecretKeySpec(keyData, "SM4");
        // 初始化Cipher对象，指定操作模式和密钥。
        cipher.init(mode, sm4Key);
        // 返回初始化后的Cipher对象。
        return cipher;
    }
}
